#include "emulator.h"
#include "emulator_priv.h"
#include "instruction.h"

#include <stdlib.h>

/*
TODOs:
	choose and implement flags
	breakpoints / watchpoints -- callback based?
*/

union virtual_address {
	int16_t i;
	struct {
		BITFIELD(uint16_t o : 12,
		BITFIELD(uint16_t p : 4,
		))
	} /* anonymous */;
};

#define PAGE_BITS 11
#define PAGE_SIZE (1<<PAGE_BITS)
#define PAGE_MASK (PAGE_SIZE-1)
union tlb {
	int16_t i;
	struct {
		//2kw (4kB) pages in a 512kw (1MB) physical space
		BITFIELD(uint16_t p : PAGE_BITS - 3,
		BITFIELD(uint16_t v : 16 - PAGE_BITS,
		BITFIELD(bool	  w : 1,
		BITFIELD(bool	  d : 1,
		BITFIELD(bool	  u : 1,
		)))))
	} /* anonymous */;
};

enum mmio {
	/*	memory-mapped io space covers the last
		sixteen words of page zero of memory.
		reading and writing mmio locations can
		have side-effects.  */

	/*	the first io port is the machine exception
		state register. when read, this has the
		value STATE.e << 10; that is, the 6-bit
		physical exception state is put into the
		high bits of a 16-bit word. both reading
		and writing have side effects. reading clears
		this register, so that further exceptions
		can be detected. writing immediately transfers
		the processor to user mode (STATE.u is set).
		note that, when writing this register, the
		instruction doing the writing is executing
		in an undefined mode (partially user and
		partially kernel), and thus must be a legal
		instruction with the same meaning in both.
		because this register is written to with
		user state restored, it must be pointed to
		by KPC, and thus the word before it must be
		in ROM. */
	IO_EX		= PAGE_SIZE - 16,

	/*	the next four io ports are aliases to
		the STATE.t tlb registers, allowing them
		to be read and written. no side-effects. */
	IO_T0		= PAGE_SIZE - 15,
	IO_T1		= PAGE_SIZE - 14,
	IO_T2		= PAGE_SIZE - 13,
	IO_T3		= PAGE_SIZE - 12,

	/*	the serial port register is next. this is
		an 8-bit register that loads unsigned into
		the low part of a word. this register is
		stuffed with the read from the serial port
		whenever such a value is. writing to this
		register does not change its value when read
		back, but does push the low 8 bits of the
		written value across the port. when this port
		is read, it is cleared, to allow the next byte
		to be received. if this port is not read by
		the time the next byte starts coming in, Bad\
		Things will happen. */
	IO_TTY		= PAGE_SIZE - 11,

};

enum exception {

	/*	software exceptions must be highest priority,
		so that if we handle exceptions in priority
		order we will not have done a task switch
		due to some other exception before we handle
		the software exception for this task. the
		intruction set guarantees that all four soft-
		ware exceptions are exclusive (at most one
		can be generated before a switch to kernel
		mode occurs), so we can encode these.
		conceptually, there are four different software
		exceptions: fetch, read, write, and syscall.
		however, it is sufficient to distinguish between
		fetch and "all the others"; in the case of a
		memory exception, the OS will have to decode the
		faulting instruction anyway to see what address
		was referenced, and checking for a syscall while
		doing this is trivial. */
	EXC_FETCH	= 1<<5,	//memory violation fetching instruction
	EXC_EVAL	= 1<<4, //read, write, or syscall

	/*	hardware exceptions are physically just
		lines, and can be hooked up as desired;
		naming here gives conventional usage.
		NMI is considered highest priority, since
		we want fast response. RTC is next, because
		it should be fast to handle, and dropping
		RTC interrupts will cause our clock to skew.
		I/O hardware comes after this, from fastest
		to slowest, more or less. */
	EXC_NMI		= 1<<3, //non-maskable interrupt button
	EXC_RTC		= 1<<2, //real time clock went bing!
	EXC_DISK	= 1<<1, //a storage device is ready
	EXC_TTY		= 1<<0, //serial port has data

};

machine_t create_machine() {
	machine_t mmm = calloc(1, sizeof(struct machine));
	if(!mmm) return 0;
	initialize_state(mmm);
	return mmm;
}

void destroy_machine(machine_t mmm) {
	free(mmm);
}

void initialize_state(machine_t mmm) {
	/* empty out snapshots */
	mmm->first_snapshot = mmm->last_snapshot = 0;

	/* initialize emulator data */
	STATE.T = 0;
	STATE.w = false;

	/* initialize processor state */
	unsigned i;
	STATE.x = false; //start in fetch mode
	STATE.u = false; //kernel mode to start
	REGISTER(PC) = 0;  //begin executing at this address
	for(i = 0; i < 4u; i++) STATE.t[i] = 0;
	for(i = 0; i < sizeof(mmm->memory) / sizeof(*mmm->memory); i++) {
		mmm->memory[i].w = 0;
		mmm->memory[i].c = true;
	}
}

struct state *get_state(machine_t mmm) {
	return &STATE;
}

typedef bool(*iterate_snapshot_helper)(machine_t, uint32_t, void *);
static void iterate_snapshots(machine_t mmm, iterate_snapshot_helper f, void *arg) {
	/* calls f(arg) on all active snapshots besides the current one */
	uint32_t current_snapshot = mmm->last_snapshot;
	while(current_snapshot != mmm->first_snapshot) {
		current_snapshot = current_snapshot ?: SNAPSHOT_COUNT; //wrap around
		--current_snapshot; //look at previous snapshot
		if(f(mmm, current_snapshot, arg)) return;
	}
}

struct scan_refcount_arg {
	uint32_t refcount;
	int32_t  paddr;
};

static bool scan_refcount_helper(machine_t mmm, uint32_t current_snapshot, void *a) {
	struct scan_refcount_arg *arg = a;
	if(mmm->snapshot[current_snapshot].w)
		if(mmm->snapshot[current_snapshot].a == arg->paddr)
			++arg->refcount;
	return false; //keep scanning
}

static uint32_t scan_refcount(machine_t mmm, int32_t paddr) {
	/* if our refcount overflowed, count manually */
	struct scan_refcount_arg arg = { refcount: 0, paddr: paddr };
	iterate_snapshots(mmm, scan_refcount_helper, &arg);
	return arg.refcount;
}

static void clear_state(machine_t mmm, bool commit) {
	/* nothing to do if no write happened here */
	if(!STATE.w) return;

	/* figure out the new refcount */
	uint32_t refcount = mmm->memory[STATE.a].w;
	if(refcount == REFCOUNT_SATURATED) refcount = scan_refcount(mmm, STATE.a);
	if(--refcount > REFCOUNT_SATURATED) refcount = REFCOUNT_SATURATED;

	/* update the global state */
	if(commit) mmm->memory[STATE.a].v = STATE.v;
	mmm->memory[STATE.a].w = (uint16_t) refcount;
	mmm->memory[STATE.a].c = false;
}

void edit_state(machine_t mmm) {
	/* claim a new snapshot slot */
	uint32_t previous_snapshot = mmm->last_snapshot++;
	mmm->last_snapshot %= SNAPSHOT_COUNT;

	/* empty it if it was previously in use */
	if(mmm->last_snapshot == mmm->first_snapshot) clear_state(mmm, true);

	/* initialize it */
	mmm->snapshot[mmm->last_snapshot] = mmm->snapshot[previous_snapshot];
	STATE.w = 0; //no write in this time slot
	STATE.S = 0; //this has not been explicitly saved
}

int16_t p_to_v(machine_t mmm, int32_t paddr, bool read, bool *error) {
	uint16_t page = paddr >> PAGE_BITS;	
	union virtual_address v = { i: paddr & PAGE_MASK };
	bool match = false;
	if(!STATE.u && (v.p == 0 || v.p == 1)) {
		//kernel has hardware mappings for page 0 and 1
		v.p = page;
		match = true;
	}
	int i;
	for(i = 0; i < 4; i++) {
		union tlb t = (union tlb) STATE.t[i];
		if(t.p == page && read ? t.v : t.w) {
			if(match) *error = true;
			match = true;
			v.p = t.v;
		}
	}
	if(!match) *error = true;
	return v.i;
}

int32_t v_to_p(machine_t mmm, int16_t addr, bool read, bool update, bool *error) {
	union virtual_address v = (union virtual_address) addr;
	int32_t p = v.o;
	bool match = false;
	if(!STATE.u && (v.p == 0 || v.p == 1)) {
		//kernel has hardware mappings for page 0 and 1
		p |= v.p << PAGE_BITS;
		match = true;
	}
	int i;
	for(i = 0; i < 4; i++) {
		union tlb t = (union tlb) STATE.t[i];
		if(t.v == v.p && read ? t.v : t.w) {
			match = true;
			p |= (int32_t) t.p << PAGE_BITS;
			if(update) {
				if(read) t.u = true;
				else	 t.d = true;
			}
		}
	}
	*error = *error || !match;
	return p;
}

struct p_mem_read_arg {
	int16_t value;
	int32_t	paddr;
};

static bool p_mem_read_helper(machine_t mmm, uint32_t current_snapshot, void *a) {
	struct p_mem_read_arg *arg = a;
	if(mmm->snapshot[current_snapshot].w)
		if(mmm->snapshot[current_snapshot].a == arg->paddr) {
			arg->value = mmm->snapshot[current_snapshot].v;
			return true; //done
		}
	return false; //keep scanning
}

int16_t p_mem_read(machine_t mmm, int32_t paddr, bool mmio) {
	switch(paddr) {
	case IO_EX: {
		int16_t ret = STATE.e << 10;
		if(mmio) STATE.e = 0;
		return ret;
		}
	case IO_T0: return STATE.t[0];
	case IO_T1: return STATE.t[1];
	case IO_T2: return STATE.t[2];
	case IO_T3: return STATE.t[3];
	case IO_TTY: {
		int16_t ret = STATE.s;
		if(mmio) STATE.s = 0;
		return ret;
		}
	default:
		if(mmm->memory[paddr].c)
			return mmm->memory[paddr].r; //recent value cache good
		if(mmm->memory[paddr].w == 0)
			return mmm->memory[paddr].v; //primordial value good
		struct p_mem_read_arg arg = { paddr: paddr };
		iterate_snapshots(mmm, p_mem_read_helper, &arg);
		mmm->memory[paddr].c = true; //update the recent cache
		return mmm->memory[paddr].r = arg.value; //with good value
	}
}

bool p_mem_write(machine_t mmm, int32_t paddr, int16_t value, bool mmio) {
	switch(paddr) {
	case IO_EX:
		if(!mmio) return false;
		return STATE.u = true;
	case IO_T0: if(mmio) STATE.t[0] = value; return mmio;
	case IO_T1: if(mmio) STATE.t[1] = value; return mmio;
	case IO_T2: if(mmio) STATE.t[2] = value; return mmio;
	case IO_T3: if(mmio) STATE.t[3] = value; return mmio;
	case IO_TTY:
		if(!mmio) return false;
		if(mmm->transient.tty_callback)
			mmm->transient.tty_callback((uint8_t) value, mmm->transient.tty_arg);
		return true;
	default:
		if(mmm->memory[STATE.a].w != REFCOUNT_SATURATED)
			mmm->memory[STATE.a].w++;
		STATE.w = true;
		STATE.a = paddr;
		STATE.v = value;
		mmm->memory[paddr].c = true;
		mmm->memory[paddr].r = value;
		return true;
	}
}

void tick(machine_t mmm) {

	edit_state(mmm);
	++STATE.T;

	union instruction ins = (union instruction) STATE.i;

	bool error = STATE.u && (ins.b.a == UPC || ins.b.b == UPC);
	bool imm   = ins.b.w && ins.b.a == ins.b.b;
	bool write = ins.b.w && !imm;
	bool read  = ins.b.a == ins.b.b && !imm;
	bool cin   = ins.b.c == CARRY_1 ?: STATE.f;

	/*	Figure out what A and B are; we need to TLB lookup. */
	int16_t a = REGISTER(ins.b.a);
	bool mem_error = false;
	int32_t paddr = v_to_p(mmm, a, read, true, &mem_error);
	error |= (read || write) && mem_error;
	int16_t b = imm
		? cin * ins.u.i
		: read
			? error ? -1 : p_mem_read(mmm, paddr, true)
			: REGISTER(ins.b.b);

	int16_t x;
	switch(ins.b.x) {
	case X_A : x = a; break;
	case X_0 : x = 0; break;
	}

	int16_t y;
	if(imm) {
		y = b;
	} else {
		switch(ins.b.y) {
		case Y_A		: y = a;		break;
		case Y_A_SHR	: y = a >> 1;	break;
		case Y_B		: y = b;		break;
		case Y_NOT_B	: y = ~b;		break;
		}
	}

	int32_t xx = x;
	int32_t yy = y;
	int32_t sum = xx + yy + cin;

	int16_t o;
	if(imm) o = (int16_t) sum;
	else switch(ins.b.o) {
		case OP_NOP: o = x;				break;
		case OP_AND: o = x & y;			break;
		case OP_IOR: o = x | y;			break;
		case OP_ADD: o = (int16_t) sum;	break;
	}

	bool f;
	if(imm) f = STATE.f;
	else switch(ins.b.f) {
		case FLAG_F:		f = STATE.f;			break;
		case FLAG_0:		f = false;				break;
		case FLAG_C:		f = !!(sum & 0x10000);	break;
		case FLAG_NOT_C:	f =  !(sum & 0x10000);	break;
		default:			/* TODO */				break;
	}

	/*	If an error occurred, record the exception.
		If we just finished an execution cycle, this
		was a read, write, or syscall. Otherwise, this
		was a read failure on a fetch. */
	if(error) STATE.e |= STATE.x ? EXC_EVAL : EXC_FETCH;

	/*	Regardless of whether we take an interrupt or
		not, we will toggle the execute bit. */
	STATE.x = !STATE.x;
	
	/*	If we have a pending interrupt, and this is
		just after an execute cycle, we need to take
		it. Otherwise, we can commit this instruction.
		Note that we already gated the write by using
		a goto to escape here in the one case that the
		write would be to a valid address but still
		illegal -- when a syscall is occurring. */
	if(STATE.u && STATE.e && !STATE.x) {
		STATE.u = false;
	} else {
		REGISTER(ins.b.a) = o;
		STATE.f = f;
		if(write) p_mem_write(mmm, paddr, b, true);
	}

	/*	If the next state is an execute, save what we just
		read to the instruction buffer. If the read failed,
		we stick the NOP that floated onto the bus in here
		instead. If we just finished an execute, we want the
		magic instruction for the next fetch. */
	static union instruction magic_instruction = { b: {
		o: OP_ADD,		//add operation
		f: FLAG_F,		//no flag write
		y: Y_A,			//alu rhs is pc
		x: X_0,			//alu lhs is zero
		c: CARRY_1,		//no carry in
		w: 0,			//no write
		b: PC,			//read in
		a: PC,			//lhs is program counter
	}};
	STATE.i = STATE.x ? b : magic_instruction.i;

}

static void unwind(machine_t mmm) {
	clear_state(mmm, false);
	if(!mmm->last_snapshot)
		mmm->last_snapshot = SNAPSHOT_COUNT;
	mmm->last_snapshot--;
}

bool can_untick(machine_t mmm) {
	if(mmm->last_snapshot == mmm->first_snapshot) return false;
	uint32_t prev_snapshot = mmm->last_snapshot ?: SNAPSHOT_COUNT;
	--prev_snapshot;
	return STATE.T != mmm->snapshot[prev_snapshot].T;
}

void untick(machine_t mmm) {
	if(can_untick(mmm)) unwind(mmm);
}

bool can_undo(machine_t mmm) {
	if(mmm->last_snapshot == mmm->first_snapshot) return false;
	uint32_t prev_snapshot = mmm->last_snapshot ?: SNAPSHOT_COUNT;
	--prev_snapshot;
	return STATE.T == mmm->snapshot[prev_snapshot].T;
}

void undo(machine_t mmm) {
	if(can_undo(mmm)) unwind(mmm);
}

void cause_nmi(machine_t mmm) {
	STATE.e |= EXC_NMI;
}

void cause_rtc(machine_t mmm) {
	STATE.e |= EXC_RTC;
}

void send_serial_byte(machine_t mmm, uint8_t byte) {
	STATE.e |= EXC_TTY;
	STATE.s = byte;
}

void recv_serial_bytes(machine_t mmm, void (*callback)(uint8_t, void *), void *arg) {
	mmm->transient.tty_callback = callback;
	mmm->transient.tty_arg = arg;
}


